{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Token types\n",
    "#\n",
    "# EOF (end-of-file) token is used to indicate that\n",
    "# there is no more input left for lexical analysis\n",
    "INTEGER, PLUS, EOF = 'INTEGER', 'PLUS', 'EOF'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Token(object):\n",
    "    def __init__(self, type, value):\n",
    "        # token type: INTEGER, PLUS, or EOF\n",
    "        self.type = type\n",
    "        # token value: 0, 1, 2. 3, 4, 5, 6, 7, 8, 9, '+', or None\n",
    "        self.value = value\n",
    "\n",
    "    def __str__(self):\n",
    "        \"\"\"String representation of the class instance.\n",
    "\n",
    "        Examples:\n",
    "            Token(INTEGER, 3)\n",
    "            Token(PLUS '+')\n",
    "        \"\"\"\n",
    "        return 'Token({type}, {value})'.format(\n",
    "            type=self.type,\n",
    "            value=repr(self.value)\n",
    "        )\n",
    "\n",
    "    def __repr__(self):\n",
    "        return self.__str__()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Interpreter(object):\n",
    "    def __init__(self, text):\n",
    "        # client string input, e.g. \"3+5\"\n",
    "        self.text = text\n",
    "        # self.pos is an index into self.text\n",
    "        self.pos = 0\n",
    "        # current token instance\n",
    "        self.current_token = None\n",
    "\n",
    "    def error(self):\n",
    "        raise Exception('Error parsing input')\n",
    "\n",
    "    def get_next_token(self):\n",
    "        \"\"\"Lexical analyzer (also known as scanner or tokenizer)\n",
    "\n",
    "        This method is responsible for breaking a sentence\n",
    "        apart into tokens. One token at a time.\n",
    "        \"\"\"\n",
    "        text = self.text\n",
    "\n",
    "        # is self.pos index past the end of the self.text ?\n",
    "        # if so, then return EOF token because there is no more\n",
    "        # input left to convert into tokens\n",
    "        if self.pos > len(text) - 1:\n",
    "            return Token(EOF, None)\n",
    "\n",
    "        # get a character at the position self.pos and decide\n",
    "        # what token to create based on the single character\n",
    "        current_char = text[self.pos]\n",
    "\n",
    "        # if the character is a digit then convert it to\n",
    "        # integer, create an INTEGER token, increment self.pos\n",
    "        # index to point to the next character after the digit,\n",
    "        # and return the INTEGER token\n",
    "        if current_char.isdigit():\n",
    "            token = Token(INTEGER, int(current_char))\n",
    "            self.pos += 1\n",
    "            return token\n",
    "\n",
    "        if current_char == '+':\n",
    "            token = Token(PLUS, current_char)\n",
    "            self.pos += 1\n",
    "            return token\n",
    "\n",
    "        self.error()\n",
    "\n",
    "    def eat(self, token_type):\n",
    "        # compare the current token type with the passed token\n",
    "        # type and if they match then \"eat\" the current token\n",
    "        # and assign the next token to the self.current_token,\n",
    "        # otherwise raise an exception.\n",
    "        if self.current_token.type == token_type:\n",
    "            self.current_token = self.get_next_token()\n",
    "        else:\n",
    "            self.error()\n",
    "\n",
    "    def expr(self):\n",
    "        \"\"\"expr -> INTEGER PLUS INTEGER\"\"\"\n",
    "        # set current token to the first token taken from the input\n",
    "        self.current_token = self.get_next_token()\n",
    "\n",
    "        # we expect the current token to be a single-digit integer\n",
    "        left = self.current_token\n",
    "        self.eat(INTEGER)\n",
    "\n",
    "        # we expect the current token to be a '+' token\n",
    "        op = self.current_token\n",
    "        self.eat(PLUS)\n",
    "\n",
    "        # we expect the current token to be a single-digit integer\n",
    "        right = self.current_token\n",
    "        self.eat(INTEGER)\n",
    "        # after the above call the self.current_token is set to\n",
    "        # EOF token\n",
    "\n",
    "        # at this point INTEGER PLUS INTEGER sequence of tokens\n",
    "        # has been successfully found and the method can just\n",
    "        # return the result of adding two integers, thus\n",
    "        # effectively interpreting client input\n",
    "        result = left.value + right.value\n",
    "        return result"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "calc> 1+3\n",
      "4\n",
      "calc> \n"
     ]
    },
    {
     "ename": "KeyboardInterrupt",
     "evalue": "Interrupted by user",
     "output_type": "error",
     "traceback": [
      "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[1;31mKeyboardInterrupt\u001b[0m                         Traceback (most recent call last)",
      "\u001b[1;32m<ipython-input-4-d9ead8cf481c>\u001b[0m in \u001b[0;36m<module>\u001b[1;34m\u001b[0m\n\u001b[0;32m     13\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m     14\u001b[0m \u001b[1;32mif\u001b[0m \u001b[0m__name__\u001b[0m \u001b[1;33m==\u001b[0m \u001b[1;34m'__main__'\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 15\u001b[1;33m     \u001b[0mmain\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m",
      "\u001b[1;32m<ipython-input-4-d9ead8cf481c>\u001b[0m in \u001b[0;36mmain\u001b[1;34m()\u001b[0m\n\u001b[0;32m      2\u001b[0m     \u001b[1;32mwhile\u001b[0m \u001b[1;32mTrue\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m      3\u001b[0m         \u001b[1;32mtry\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m----> 4\u001b[1;33m             \u001b[0mtext\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0minput\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m'calc> '\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m      5\u001b[0m         \u001b[1;32mexcept\u001b[0m \u001b[0mEOFError\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m      6\u001b[0m             \u001b[1;32mbreak\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n",
      "\u001b[1;32m~\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.8_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python38\\site-packages\\ipykernel\\kernelbase.py\u001b[0m in \u001b[0;36mraw_input\u001b[1;34m(self, prompt)\u001b[0m\n\u001b[0;32m    858\u001b[0m                 \u001b[1;34m\"raw_input was called, but this frontend does not support input requests.\"\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m    859\u001b[0m             )\n\u001b[1;32m--> 860\u001b[1;33m         return self._input_request(str(prompt),\n\u001b[0m\u001b[0;32m    861\u001b[0m             \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_parent_ident\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m    862\u001b[0m             \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_parent_header\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n",
      "\u001b[1;32m~\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.8_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python38\\site-packages\\ipykernel\\kernelbase.py\u001b[0m in \u001b[0;36m_input_request\u001b[1;34m(self, prompt, ident, parent, password)\u001b[0m\n\u001b[0;32m    902\u001b[0m             \u001b[1;32mexcept\u001b[0m \u001b[0mKeyboardInterrupt\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m    903\u001b[0m                 \u001b[1;31m# re-raise KeyboardInterrupt, to truncate traceback\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m--> 904\u001b[1;33m                 \u001b[1;32mraise\u001b[0m \u001b[0mKeyboardInterrupt\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m\"Interrupted by user\"\u001b[0m\u001b[1;33m)\u001b[0m \u001b[1;32mfrom\u001b[0m \u001b[1;32mNone\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m    905\u001b[0m             \u001b[1;32mexcept\u001b[0m \u001b[0mException\u001b[0m \u001b[1;32mas\u001b[0m \u001b[0me\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m    906\u001b[0m                 \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mlog\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mwarning\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34m\"Invalid Message:\"\u001b[0m\u001b[1;33m,\u001b[0m \u001b[0mexc_info\u001b[0m\u001b[1;33m=\u001b[0m\u001b[1;32mTrue\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n",
      "\u001b[1;31mKeyboardInterrupt\u001b[0m: Interrupted by user"
     ]
    }
   ],
   "source": [
    "def main():\n",
    "    while True:\n",
    "        try:\n",
    "            text = input('calc> ')\n",
    "        except EOFError:\n",
    "            break\n",
    "        if not text:\n",
    "            continue\n",
    "        interpreter = Interpreter(text)\n",
    "        result = interpreter.expr()\n",
    "        print(result)\n",
    "\n",
    "\n",
    "if __name__ == '__main__':\n",
    "    main()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.8"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
